문단 클릭 = 시작 위치 선택 · 재생은 우하단 버튼/P (재생 중 클릭 = 점프)

3장. 멀티모달 RAG를 활용한 복합 데이터 처리

출처 — 브라이스 유·조경아·박수진·김재웅, 『RAG 마스터: 랭체인으로 완성하는 LLM 서비스』(프리렉, 2025), 3장 (pp. 171~206). 원문 PDF rag_master_final_v11_260910.pdf (2026-09-10 판)

텍스트만으로는 놓치는 이미지·차트·표까지 검색 대상에 넣는 멀티모달 RAG의 개념과 세 가지 구현 방식을 살펴보고, unstructured로 PDF를 텍스트·테이블·이미지로 나눠 각각을 요약한 뒤 멀티벡터 검색기(MultiVectorRetriever)로 묶어 실제로 동작하는 RAG 파이프라인까지 완성한다.

학습 목표

이 장을 끝내면 다음을 할 수 있다.

  • 멀티모달 RAG가 필요한 이유와, 일반 RAG보다 어려운 두 가지 지점(유형별 요구사항의 차이·데이터 형식 간 일관성)을 설명한다.
  • 멀티모달 RAG를 구현하는 세 가지 접근(동일 벡터 공간·기본 모달리티 통일·별도 저장소)의 장단점을 비교해 상황에 맞게 선택한다.
  • unstructuredpartition_pdf로 PDF에서 텍스트·테이블·이미지를 추출하고, 각각을 검색에 최적화된 형태로 요약한다.
  • docstore(원본)와 vectorstore(요약 임베딩)를 분리해 멀티벡터 검색기(MultiVectorRetriever)를 구성하는 원리를 설명한다.
  • 검색된 멀티모달 컨텍스트를 MLLM에 전달해 답변을 생성하는 RAG 체인을 LCEL로 구현한다.

전체 흐름도

[ 멀티모달 RAG란 ]
      │  왜: 상품 사진·차트·표처럼 텍스트만으론 놓치는 정보가 있다
      │  난점 ①: 유형별 요구사항이 다르다 (단순 이미지 vs 정보밀집 차트)
      │  난점 ②: 형식 간 일관성 — 차트와 그 설명 텍스트가 어긋나면 안 된다
      ▼
[ 구현 방법 — 세 가지 접근 ]
      ├─ ① 동일 벡터 공간(CLIP)   — 임베딩 모델만 교체, 구현 쉬움 / 통합 모델 확보 어려움
      ├─ ② 기본 모달리티로 통일   — 이미지→텍스트 요약 후 임베딩 / 세부 뉘앙스 손실
      └─ ③ 별도 저장소 + 리랭킹  — ①②를 결합, 품질↑ / 전처리·인프라 비용↑
      ▼
[ 실습 — ②번 방식으로 구현 ]
      │
      ├─ 환경 설정: tesseract → poppler → unstructured[all-docs] 순서 설치, API 키 로드
      │
      ├─ 데이터 전처리: partition_pdf 로 텍스트·테이블·이미지 추출
      │        └→ 텍스트·테이블 요약(LLM) + 이미지 요약(MLLM, base64)
      │
      ├─ 멀티 벡터 검색기 구성
      │        docstore(원본 3종, uuid) ── id_key ──> vectorstore(요약 임베딩)
      │        └→ MultiVectorRetriever 가 둘을 연결
      │
      └─ 검색과 답변 생성
               질문 → retriever.invoke → 이미지·텍스트 분리(split_image_text_types)
               → prompt_func 로 문맥 구성 → gpt-4o(MLLM) → 답변
      ▼
[ 4장: 이 검색기를 청킹 전략·질의 변형·리랭킹으로 고도화 ]

0. 용어 사전

선행 용어 — 이 장을 읽기 전에

아래 5개를 모르면 이 장의 실습 코드가 막힌다. 낯설면 1장·2장을 먼저 보면 좋다. 판정 기준 — 「이 말을 모르면 이 장을 못 읽는가」. 이 장에서 처음 설명하는 개념(모달리티·CLIP·MLLM 등)은 선행이 아니다 — 본문의 몫이므로 아래 표에 없다.

한글 용어 원문 영문명 의미 (정의 · 비유 · 본문 위치)
임베딩 Embedding 텍스트·이미지 같은 원본 데이터의 의미를 숫자 벡터로 담은 표현(2장 §1). 이 장 전체가 텍스트 요약·이미지 요약을 임베딩해 벡터저장소에 넣는 일을 반복한다. 지문을 숫자로 바꿔 대조하는 것에 비유. 본문 §4·§5
벡터 저장소(벡터 데이터베이스) Vector Store / Vector Database 임베딩 벡터를 저장하고 유사도로 검색하는 저장소, 이 책은 크로마(Chroma)를 씀(2장 §4). 이 장 §5에서 요약 임베딩을 담는 vectorstore가 바로 이것이다. 본문 §5
러너블(Runnable)과 LCEL Runnable / LangChain Expression Language \| 연산자로 여러 구성 요소를 파이프라인처럼 이어 붙이는 랭체인의 실행 문법(1장 §3). 이 장 §6의 chain = {...} \| RunnableLambda(...) \| model \| StrOutputParser() 가 그대로 이 문법이다. 본문 §4·§6
프롬프트 템플릿 Prompt Template 변수 자리({element} 등)를 둔 프롬프트 문자열을 만들어 재사용하는 랭체인 구성 요소(1장 §4). §4의 요약 체인이 ChatPromptTemplate.from_template으로 만든다. 본문 §4
출력 파서 Output Parser 모델의 응답을 원하는 형태(문자열 등)로 변환하는 랭체인 구성 요소(1장 §5). §4·§6의 체인 끝에 붙는 StrOutputParser()가 이것이다. 본문 §4·§6

참고 — 이 장은 실습 코드 비중이 책의 다른 장보다 높다. 코드 한 줄 한 줄보다 "왜 두 저장소로 나누는가"·"왜 요약을 임베딩하는가" 같은 설계 이유에 먼저 집중해도 좋다.

이 장의 용어 전부 — 찾아보기

읽다가 막히면 여기서 찾는다. 위 선행 5개는 다시 적지 않는다. 본문 등장 순서로 둔다.

한글 용어 원문 영문명 의미
모달리티 Modality 이미지·텍스트·음성 등 입력받거나 처리하는 데이터의 형태
멀티모달 Multimodal 서로 다른 유형의 데이터를 동시에 사용하는 방식
멀티모달 RAG Multimodal RAG 텍스트뿐 아니라 이미지·차트·테이블까지 포함해 검색·생성하는 RAG
멀티모달 LLM(MLLM) Multi-Modal LLM 이미지 등 여러 모달리티를 직접 입력받아 이해·생성할 수 있는 LLM
CLIP Contrastive Language-Image Pretraining 이미지·텍스트를 대조 학습으로 같은 벡터 공간에 매핑하는 오픈AI의 멀티모달 모델
대조 학습 Contrastive Learning 짝이 맞는 쌍은 가깝게, 아니면 멀게 벡터를 조정하며 학습하는 방식
리랭킹 ReRanking 여러 검색 결과 중 상위 n개의 가장 유사한 문서를 다시 골라내는 후처리
tesseract Tesseract PDF·이미지에서 문자열을 인식하는 광학 문자 인식(OCR) 라이브러리
poppler Poppler PDF 렌더링·처리에 필요한 라이브러리
unstructured unstructured 비정형 문서(PDF 등)에서 텍스트·표·이미지를 유형별로 추출하는 파이썬 라이브러리
partition_pdf partition_pdf unstructured 라이브러리에서 PDF 요소(텍스트·표·이미지)를 추출하는 함수
chunking_strategy chunking_strategy 텍스트를 조각내는 전략. basic(글자 수 기준)과 by_title(섹션·페이지 경계 기준)
의미 기반 검색 Semantic Retrieval 임베딩 벡터의 유사도로 관련 문서를 찾는 검색 방식
base64 인코딩 Base64 Encoding 이진 데이터를 64개의 ASCII 문자로 표현하는 텍스트 인코딩 방식
detail 파라미터 detail Parameter 오픈AI 비전 API에서 이미지 처리 품질·토큰 비용을 조절하는 값(low·high·auto)
멀티벡터 검색기 MultiVectorRetriever 원본 저장소(docstore)와 요약 임베딩 저장소(vectorstore)를 id_key로 연결해 검색하는 랭체인 검색기
docstore Docstore 원본 데이터(텍스트·테이블·이미지)를 그대로 저장하는 저장소. 이 실습은 InMemoryStore 사용
id_key id_key vectorstore의 요약 문서와 docstore의 원본 데이터를 연결하는 메타데이터 키
RunnableLambda RunnableLambda 사용자 정의 함수(람다)를 체인 안에서 실행할 수 있게 감싸는 러너블
RunnablePassthrough RunnablePassthrough 입력을 가공 없이 그대로 다음 단계로 전달하는 러너블

1. 멀티모달 RAG란 무엇이고 왜 필요한가

지금까지 다룬 RAG는 텍스트 데이터만으로 정보를 검색했다. 하지만 현실 세계의 데이터는 텍스트뿐 아니라 이미지·음성 등 다양한 형태로 존재한다. 모달리티(modality)는 입력받거나 처리하는 데이터의 형태(이미지·텍스트·음성 등)를 뜻하고, 멀티모달(multimodal)은 이런 서로 다른 유형의 데이터를 동시에 사용하는 방식을 뜻한다. 멀티모달 RAG는 단순 텍스트 정보에만 의존하지 않고 이미지나 차트, 테이블 등의 시각적·구조적 데이터까지 포함해 정보를 검색하고 생성하는 기법이다.

왜 굳이 다양한 데이터를 함께 활용해야 하나. 온라인 쇼핑몰에서 상품을 검색하는 상황을 생각해 보자. 텍스트 데이터인 상품 설명뿐 아니라 상품 사진 같은 시각적 정보를 함께 사용할 수 있다면, 원하는 상품을 더 정확하게 찾을 수 있다. 마찬가지로 LLM에 텍스트뿐 아니라 이미지·차트·표까지 풍부한 정보를 제공하면 관련 문서를 검색할 때 더 정확한 맥락을 파악할 수 있고, 답변을 생성할 때도 단순 텍스트를 넘어 다차원적인 정보를 활용해 더 정확하고 풍부한 답변을 제공할 수 있다.

어려운 이유 ① — 유형별로 요구사항이 다르다. 실무의 비정형 데이터는 고해상도 이미지가 담긴 슬라이드, 텍스트·표·차트가 섞인 문서 등으로 존재하며 PDF 등 다양한 형식으로 흩어져 있는 경우가 많다. 예를 들어 연못·바다·나무처럼 세부 디테일보다 전반적인 장면이 중요한 이미지는 주요 요소만 강조해 분석하면 충분하다. 반면 보고서의 차트나 다이어그램처럼 정보가 밀집된 이미지는 각 수치가 의미하는 바를 해석하는 세부 분석과, 그 분석이 이루어진 환경 같은 추가 맥락까지 함께 고려해야 한다. 파이프라인을 설계할 때 각 형식의 특성과 요구사항을 인식하고 그에 맞춰 처리해야 정보를 효율적으로 담을 수 있다.

어려운 이유 ② — 데이터의 일관성. 서로 다른 형식의 데이터를 일관성 있게 관리하는 것도 중요한 과제다. 문서 내에서 차트를 설명하는 텍스트와 차트 자체의 의미가 일치하는지 확인해야 한다. LLM에 전달되는 두 유형의 정보(텍스트와 차트)가 서로 상반된다면 결국 사용자에게 전달되는 답변의 정확도가 떨어질 수 있다. 따라서 다양한 데이터 형식 간에 일관성을 유지하고 이를 효과적으로 검증하는 과정이 필요하다.

2. 멀티모달 RAG 구현 방법 — 세 가지 접근

멀티모달 RAG 파이프라인을 구축할 때는 서로 다른 모달리티를 어떻게 LLM에 인지시킬지에 따라 크게 세 가지 접근 방식이 있다.

  • 모든 모달리티를 동일한 벡터 공간에 포함하기
  • 모든 모달리티를 하나의 기본 모달리티로 묶기
  • 서로 다른 모달리티를 별도의 저장소에서 다루기

2.1 모든 모달리티를 동일한 벡터 공간에 포함하기

이미지와 텍스트를 모두 효과적으로 처리하려면 CLIP(Contrastive Language-Image Pretraining)과 같은 멀티모달 모델을 사용해 두 모달리티를 동일한 벡터 공간에 인코딩할 수 있다. 이를 통해 이미지와 텍스트를 같은 방식으로 임베딩할 수 있으며, 기존 텍스트 기반 RAG 인프라를 그대로 유지하면서 임베딩 모델만 교체해 다양한 모달리티를 수용할 수 있다.

통합된 벡터 공간에서는 유사도 검색으로 이미지와 텍스트를 모두 검색할 수 있고, 생성 단계에서는 멀티모달 LLM(MLLM)이 이미지와 텍스트를 모두 활용해 답변을 생성한다. 이 방식은 이전 장의 RAG 파이프라인에서 임베딩 모델을 교체하는 것 외에 별다른 변화를 요구하지 않으므로 구현하기 쉽다는 장점이 있다. 다만 이미지·텍스트·복잡한 표 등 다양한 콘텐츠를 정확하게 임베딩할 수 있는 통합 모델을 확보하는 것이 관건이다.

원문 — CLIP의 구조 (공식 저장소 · 원논문 Radford et al., 2021)

CLIP은 대규모 텍스트-이미지 데이터셋으로 학습되어, 주어진 이미지에 맞는 텍스트를 예측하거나 텍스트에 맞는 이미지를 찾는 데 쓰인다. 구조는 크게 이미지를 벡터로 바꾸는 이미지 인코더(주로 ResNet·Vision Transformer)와 텍스트를 벡터로 바꾸는 텍스트 인코더(주로 Transformer 기반 언어 모델) 두 가지로 구성되며, 두 인코더가 만든 벡터를 비교하는 대조 학습으로 이미지-텍스트 쌍이 맞으면 유사하게, 아니면 다르게 벡터가 조정되도록 학습한다.

CLIP의 특징 세 가지 — 일반화 능력(특정 작업에 훈련되지 않아도 다양한 이미지-텍스트 작업에서 강력한 성능), Zero-shot 학습(추가 훈련 없이도 새 카테고리의 이미지를 적절히 설명), 멀티모달 학습(이미지 검색·이미지 설명 생성·멀티모달 챗봇 등에 적용 가능).

2.2 모든 모달리티를 하나의 기본 모달리티로 표현하기

이 방식은 모든 데이터를 한 가지 유형의 데이터로 표현하는 것이다. 예를 들어 이미지와 텍스트를 함께 처리할 때 멀티모달 LLM으로 이미지를 설명하는 텍스트 요약을 생성하고, 이 텍스트를 임베딩해 검색한 뒤 이미지를 포함한 검색 결과를 바탕으로 LLM이 답변을 생성한다. 요약하면 주된 모달리티 하나(주로 텍스트)를 기본으로 삼고, 다른 모든 모달리티를 그 기본 모달리티로 표현하는 전략이다.

장점은 이미지에서 생성된 메타데이터가 명확하고 객관적인 질문에 효과적으로 대응할 수 있다는 것과, 이미지 임베딩을 위한 새 모델을 따로 조정하거나 검색 결과 순위를 조정하는 추가 작업이 필요 없다는 것이다. 다만 전처리 과정에서 비용이 발생하고, 이미지의 세부적인 뉘앙스나 정보가 손실될 수 있다는 한계가 있다. 이번 장의 실습은 이 방식을 오픈AI의 MLLM으로 구현한다.

2.3 서로 다른 모달리티를 별도의 저장소에서 다루기

마지막 방식은 앞선 두 방식을 결합한 형태다. 이미지와 텍스트를 모두 다루는 멀티모달 임베딩 모델의 결과는 벡터 저장소 1에, 이미지를 텍스트로 묘사한 텍스트 임베딩 모델의 결과는 벡터 저장소 2에 저장한다. 사용자가 이미지·텍스트로 구성된 질문을 입력하면 두 임베딩 벡터를 각각 얻어, 첫 번째로는 벡터 저장소 1에서, 두 번째로는 벡터 저장소 2에서 유사한 문서를 검색한다.

이렇게 얻은 검색 결과를 모두 사용하거나 이들 사이에서 리랭킹(ReRanking)을 거쳐 상위 n개의 가장 유사한 문서를 가려낸 뒤, 최종적으로 LLM에 답변 생성 맥락으로 제공한다. 이 방식은 앞선 방식들보다 전처리에 시간이 더 걸리고 인프라 비용도 배로 들지만, 각 방식의 장점을 결합해 가장 유사한 문서만 가려내 쓰므로 가장 질적으로 우수한 답변을 제공할 수 있다.

3. 실습 준비 — 환경 설정과 라이브러리

이제 실제로 PDF 파일에서 데이터를 추출하고 저장한 뒤, PDF 본문에 대한 질문과 응답이 가능한 검색기를 구현한다. 오픈AI의 MLLM을 이용해 쉽게 구현할 수 있는 '모든 모달리티를 하나의 기본 모달리티로 표현하는 방식'(§2.2)을 사용한다. 코드는 구글 코랩에 작성하며, 책의 깃허브 저장소에서 내려받은 실습 코드는 3장 폴더의 ch03_MULTI_MODAL_RAG.ipynb 파일이다.

라이브러리 설치.

%capture --no-stderr
!pip install -U langchain openai chromadb langchain-experimental pillow pydantic lxml matplotlib tiktoken

tesseract 설치 — PDF에서 문자열을 추출하는 광학 문자 인식(OCR) 라이브러리. 운영체제별 설치 방법이 다르다.

  • Windowstesseract GitHub 저장소에서 Tesseract 3.05·4·5의 설치 프로그램을 내려받는다. 학습 도구가 포함되어 있으며 32비트·64비트 설치 프로그램을 모두 제공한다.
  • macOSbrew install tesseract
  • Linuxsudo apt install tesseract-ocr · sudo apt install libtesseract-dev

poppler 설치 — PDF 렌더링·처리 라이브러리.

  • Windowspoppler GitHub 저장소에서 최신 설치 프로그램을 내려받아 압축을 풀고, 폴더경로\bin을 시스템 환경 변수 Path에 추가한다. pdftoppm 명령어가 동작하면 설치 성공이다.
  • macOSbrew install poppler
  • Linuxsudo apt-get install poppler-utils

unstructured 설치. tesseract·poppler에 의존성을 가지므로 반드시 이 둘을 먼저 설치한 뒤 설치한다.

%capture --no-stderr
!pip install -U "unstructured[all-docs]"

참고 — 구글 코랩에서 unstructured 설치. 코랩의 파이썬 실행 커널은 리눅스 우분투 운영체제 위에서 동작하므로, Linux 설치 안내를 그대로 따르면 성공한다. 코랩에서는 명령어 앞에 !(느낌표)를 붙이면 코드 셀에서 리눅스 명령어를 실행할 수 있다.

text !sudo apt install tesseract-ocr !sudo apt install libtesseract-dev !sudo apt-get install poppler-utils !pip install -U "unstructured[all-docs]"

오픈AI 키 설정. 이번 실습은 ChatGPT API를 사용하므로 OPENAI_API_KEY를 준비해 .env 파일에 추가하고 로드한다.

from google.colab import drive
drive.mount('/content/drive')

from dotenv import load_dotenv
# .env 파일에서 환경 변수 로드
load_dotenv("/content/.env")

4. 데이터 전처리 — 추출과 요약

먼저 검색에 활용할 PDF 파일을 전처리해 벡터 저장소에 저장하는 작업을 진행한다. PDF 안에는 텍스트·이미지·테이블 등 다양한 유형의 데이터가 존재하므로, 이를 한 번에 추출한 뒤 유형별로 분류해 저장한다. 이번 예제는 질병관리청이 주마다 발간하는 말라리아 주간소식지 PDF(책의 깃허브 저장소 3장 폴더의 sample.pdf)를 사용한다. 이 PDF는 말라리아 발생 현황 텍스트, 이를 도식화한 테이블 데이터, 발생 분포를 표현한 이미지 데이터를 포함하고 있다.

# 파일 경로
fpath = '/content/drive/MyDrive/langchain-tutorial/Ch03. MultiModal RAG'
fname = "sample.pdf"

요소 추출. unstructured는 텍스트 전처리에 nltk를 사용하므로 필요한 데이터를 먼저 내려받는다.

import nltk
# nltk 필요 데이터 다운로드
nltk.download('punkt_tab')
nltk.download('averaged_perceptron_tagger_eng')

이어서 partition_pdf 함수로 요소 추출을 진행한다.

from unstructured.partition.pdf import partition_pdf
import os

# PDF에서 요소 추출
raw_pdf_elements = partition_pdf(
    filename=os.path.join(fpath, fname),
    extract_images_in_pdf=True,
    infer_table_structure=True,
    chunking_strategy="by_title",
    extract_image_block_output_dir=fpath,
)

partition_pdf의 주요 인자는 다음과 같다.

  • filename — 분석할 PDF 파일 경로.
  • extract_images_in_pdf — PDF 안에 이미지가 있으면 추출한다.
  • infer_table_structure — PDF 안에 테이블이 있으면 구조를 추출한다.
  • chunking_strategy — 텍스트를 조각내는 전략. basic은 섹션 구분 없이 글자 수 기준으로, by_title은 페이지·섹션 경계 기준으로 조각낸다(공식 chunking 전략 문서).
  • extract_image_block_output_dir — 추출한 이미지를 저장할 경로.

추출된 각 element의 타입 문자열을 검사해 테이블과 텍스트를 분리한다. 타입에 unstructured.documents.elements.Table이 포함되면 테이블, unstructured.documents.elements.CompositeElement가 포함되면 텍스트다.

# 텍스트, 테이블 추출
tables = []
texts = []
for element in raw_pdf_elements:
    if "unstructured.documents.elements.Table" in str(type(element)):
        tables.append(str(element))       # 테이블 요소 추가
    elif "unstructured.documents.elements.CompositeElement" in str(type(element)):
        texts.append(str(element))        # 텍스트 요소 추가

tables[0]·texts[0]을 확인하면 unstructured가 표·문단을 그대로 읽어 낸 원본 데이터라, 줄바꿈이 뒤섞이고 숫자와 항목명이 붙어 있어 가독성이 떨어지는 모습을 볼 수 있다 — 예를 들어 연도별 확진자 수 표는 '2023년 2022년 2024년 42주 2021년 2020년 747 294 420 전체 385 665 356 274 630 382 673 국내발생 74 20 35 29 38 해외유입'처럼 열 구분이 사라진 문자열로 나온다. 바로 이 가시성 문제 때문에, 다음 절에서 LLM으로 요약을 생성해 검색에 쓴다.

텍스트 및 테이블 요약

추출한 요소는 복잡한 테이블과 텍스트를 그대로 읽은 데이터라 가시성이 떨어지므로, LLM에 제공해 요약문을 생성한다.

from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI

# 프롬프트 설정
prompt_text = """당신은 표와 텍스트를 요약하여 검색할 수 있도록 돕는 역할을 맡은 어시스턴트입니다.
이 요약은 임베딩되어 원본 텍스트나 표 요소를 검색하는 데 사용될 것입니다.
표 또는 텍스트에 대한 간결한 요약을 제공하여 검색에 최적화된 형태로 만들어 주세요.
표 또는 텍스트: {element}"""
prompt = ChatPromptTemplate.from_template(prompt_text)

# 텍스트 요약 체인
model = ChatOpenAI(temperature=0, model="gpt-4")
summarize_chain = {"element": lambda x: x} | prompt | model | StrOutputParser()

# 제공된 텍스트에 대해 요약을 할 경우
text_summaries = summarize_chain.batch(texts, {"max_concurrency": 5})
# 요약을 원치 않을 경우
# text_summaries = texts

# 제공된 테이블에 적용
table_summaries = summarize_chain.batch(tables, {"max_concurrency": 5})

summarize_chainelement라는 키에 저장된 리스트 데이터를 입력받아 모델에 넣어 문자열 형태의 출력을 반환받는 체인이다(선행 용어 — 러너블·LCEL). batch 함수는 여러 입력을 한 번에 처리할 수 있어, 앞서 추출한 테이블과 텍스트를 한 번의 호출로 요약한다. 요약 결과를 확인하면 테이블은 "특정 연도별 주차에 따른 국내발생·해외유입 확진자 수를 나타내는 통계 데이터"처럼, 텍스트는 "42주차 말라리아 환자 발생 현황 보고서에 따르면 총 665명이 발생했으며..."처럼 훨씬 압축적이고 명확한 형태로 바뀐다.

이미지 요약

텍스트와 테이블 요약을 마쳤으니, 마지막 모달리티인 이미지 요약을 생성한다. 오픈AI가 지원하는 모델 중 비전(이미지 이해)을 지원하는 gpt-4o 모델을 사용한다.

참고 — 오픈AI의 MLLM(Multi-Modal LLM). 오픈AI의 모든 LLM이 이미지를 처리할 수 있는 것은 아니다. 모델 소개 페이지에서 비전을 지원한다고 명시된 모델만 이미지를 입력으로 받아 인식·출력할 수 있다. 이미지를 모델에 전달하는 방법은 여러 가지가 있다.

  • URL로 전달image_url 타입 콘텐츠에 이미지의 url을 직접 지정한다.
  • base64로 전달 — 이미지를 base64로 인코딩해 data:image/jpeg;base64,{...} 형식의 url로 지정한다. PDF에서 추출한 이미지처럼 외부에 공개된 URL이 없는 경우 이 방식을 쓴다.
  • 여러 장 전달content 리스트에 image_url 타입 콘텐츠를 여러 개 추가하면 한 번에 여러 이미지를 비교시킬 수 있다.
  • 품질 지정(detail)image_url"detail": "low"처럼 지정해 처리 품질을 조절한다. low는 512×512 저해상도 이미지를 85토큰으로 표현해 빠르고 저렴하며, high는 85토큰으로 먼저 확인한 뒤 512×512 타일당 170토큰을 더 써서 세부 크롭까지 분석한다. auto는 입력 이미지 크기에 따라 이 둘을 자동으로 고른다.

이번 실습은 PDF에서 추출한 이미지를 다루므로 base64 인코딩 방식을 사용한다.

import base64

def encode_image(image_path) -> str:
    # 이미지 base64 인코딩
    with open(image_path, "rb") as image_file:
        return base64.b64encode(image_file.read()).decode('utf-8')

# 이미지의 base64 인코딩을 저장하는 리스트
img_base64_list = []

# 이미지를 읽어 base64 인코딩 후 저장
for img_file in sorted(os.listdir(fpath)):
    if img_file.endswith('.jpg'):
        img_path = os.path.join(fpath, img_file)
        base64_image = encode_image(img_path)
        img_base64_list.append(base64_image)

참고 — Base64 인코딩. 이진 데이터를 텍스트 형식으로 변환하는 방법으로, 바이너리 데이터를 네트워크로 전송하거나 텍스트로 저장할 때 쓴다. 8비트 단위의 원본 데이터를 6비트 단위로 나눠 대문자(A-Z)·소문자(a-z)·숫자(0-9)·+·/로 이루어진 64개의 문자로 표현하며, 3바이트로 나누어떨어지지 않으면 =로 패딩해 길이를 맞춘다.

이미지를 모두 인코딩했다면 LLM에 전달해 텍스트 형식의 요약문을 생성한다. 이 요약문은 원본 이미지를 검색하는 데 쓰인다.

from langchain_core.messages import HumanMessage
from langchain_openai import ChatOpenAI

def image_summarize(img_base64: str) -> str:
    # 이미지 요약
    chat = ChatOpenAI(model="gpt-4o", max_tokens=1024)
    prompt = """
당신은 이미지를 요약하여 검색을 위해 사용할 수 있도록 돕는 어시스턴트입니다.
이 요약은 임베딩되어 원본 이미지를 검색하는 데 사용됩니다.
이미지 검색에 최적화된 간결한 요약을 작성하세요.
"""
    msg = chat.invoke(
        [
            HumanMessage(
                content=[
                    {"type": "text", "text": prompt},
                    {
                        "type": "image_url",
                        "image_url": {"url": f"data:image/jpeg;base64,{img_base64}"},
                    },
                ]
            )
        ]
    )
    return msg.content

# 이미지 요약을 저장하는 리스트
image_summaries = []
for img_base64 in img_base64_list:
    image_summary = image_summarize(img_base64)
    image_summaries.append(image_summary)

image_summaries[0]을 확인하면 '2024년 말라리아 주간 소식지, 42주차(10.13~10.19), 질병관리청 KDCA, 말라리아 감시 보고서.'처럼 이미지가 무엇인지 검색에 쓸 수 있는 간결한 문장으로 요약된 것을 볼 수 있다.

5. 멀티 벡터 검색기 구성

이제 추출한 요소들을 벡터화해 저장하고, 멀티모달 데이터를 검색할 수 있는 멀티벡터 검색기(MultiVectorRetriever)를 구성한다. 벡터 저장소는 크로마(Chroma)를 사용한다.

from langchain.retrievers import MultiVectorRetriever
from langchain_core.stores import InMemoryStore
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import Chroma

# 분할한 텍스트들을 색인할 벡터 저장소
vectorstore = Chroma(collection_name="multi_modal_rag",
                      embedding_function=OpenAIEmbeddings())

# 원본 문서 저장을 위한 저장소 선언
docstore = InMemoryStore()
id_key = "doc_id"

# 검색기
retriever = MultiVectorRetriever(
    vectorstore=vectorstore,
    docstore=docstore,
    id_key=id_key,
)

크로마 DB를 선언할 때 embedding_functionOpenAIEmbeddings()를 지정하면, 저장할 때 오픈AI 임베딩 API를 자동으로 사용해 벡터로 변환한 뒤 저장한다. 원본 데이터는 메모리 저장소인 InMemoryStore()(docstore)에 저장한다. docstore(원본)와 vectorstore(요약 임베딩)를 분리해 두고, 검색은 검색에 최적화된 요약으로 빠르게 하면서 최종 답변 생성에는 손실 없는 원본을 쓸 수 있게 하는 것이 이 검색기의 핵심 설계다.

원본 데이터 저장. 각 원본을 식별할 고유 id가 필요하므로 uuid.uuid4()로 생성한다.

import uuid

# 원본 텍스트 데이터 저장
doc_ids = [str(uuid.uuid4()) for _ in texts]
retriever.docstore.mset(list(zip(doc_ids, texts)))

# 원본 테이블 데이터 저장
table_ids = [str(uuid.uuid4()) for _ in tables]
retriever.docstore.mset(list(zip(table_ids, tables)))

# 원본 이미지(base64) 데이터 저장
img_ids = [str(uuid.uuid4()) for _ in img_base64_list]
retriever.docstore.mset(list(zip(img_ids, img_base64_list)))

요약 임베딩 저장. vectorstore에는 텍스트·테이블·이미지의 요약본을 저장한다. 이때 메타데이터에 {id_key: 원본_id}를 함께 넣어, 나중에 요약으로 검색된 결과를 원본과 연결할 수 있게 한다.

from langchain.schema.document import Document

# 텍스트 요약 벡터 저장
summary_texts = [
    Document(page_content=s, metadata={id_key: doc_ids[i]})
    for i, s in enumerate(text_summaries)
]
retriever.vectorstore.add_documents(summary_texts)

# 테이블 요약 벡터 저장
summary_tables = [
    Document(page_content=s, metadata={id_key: table_ids[i]})
    for i, s in enumerate(table_summaries)
]
retriever.vectorstore.add_documents(summary_tables)

# 이미지 요약 벡터 저장
summary_img = [
    Document(page_content=s, metadata={id_key: img_ids[i]})
    for i, s in enumerate(image_summaries)
]
retriever.vectorstore.add_documents(summary_img)

문서가 벡터 저장소에 저장되면 생성된 id_key 값들의 리스트(['e05355d4-2782-4378-...', '25563971-05f0-4413-...', ...])가 출력되며, 이 값이 출력되면 요약 벡터가 성공적으로 저장된 것이다.

주의 — InMemoryStore의 한계. 이름 그대로 데이터를 메모리에만 저장하므로 프로세스가 종료되면 docstore에 저장한 원본 텍스트·테이블·이미지가 모두 사라진다. 실습을 넘어 운영 환경에 적용하려면 디스크나 데이터베이스 기반의 영속적인 저장소로 교체해야 한다.

6. 검색과 답변 생성 — 멀티모달 RAG 완성

멀티모달 데이터를 검색할 수 있는 검색기가 준비됐으니, 이를 기반으로 정확하고 풍부한 답변을 생성하는 RAG 파이프라인을 완성한다.

검색 확인. 검색기가 질문에 유사한 이미지·텍스트·테이블을 잘 검색하는지 먼저 확인한다.

docs = retriever.invoke("말라리아 군집 사례는 어떤가요?")
len(docs)
4

총 4개의 검색 결과가 나오며, 이미지와 텍스트 결과가 섞여 있으므로 구분해서 확인한다.

from base64 import b64decode

def split_image_text_types(docs):
    # 이미지와 텍스트 데이터를 분리
    b64 = []
    text = []
    for doc in docs:
        try:
            b64decode(doc)
            b64.append(doc)
        except Exception:
            text.append(doc)
    return {"images": b64, "texts": text}

docs_by_type = split_image_text_types(docs)

b64decode가 성공하면 base64 이미지로, 예외가 발생하면 텍스트로 판별한다. 이번 검색에서는 이미지 3개, 텍스트 1개가 나온다 — vectorstore 하나에 텍스트·테이블·이미지 요약이 모두 함께 임베딩되어 있어, 질문과 유사도가 높은 항목이 모달리티와 무관하게 함께 검색되기 때문이다.

from IPython.display import display, HTML

def plt_img_base64(img_base64):
    # base64 이미지로 html 태그를 작성
    image_html = f'<img src="data:image/jpeg;base64,{img_base64}" />'
    # html 태그를 기반으로 이미지를 표시
    display(HTML(image_html))

plt_img_base64(docs_by_type["images"][0])

첫 번째 이미지를 확인하면 "3 말라리아 군집사례 현황"이라는 소제목 이미지가 검색된다. 질문에 대한 답변을 작성하기에 유의미한 이미지는 아니지만, 질문과 연관된 이미지가 제대로 검색되었음을 확인할 수 있다. 텍스트 결과(docs_by_type["texts"][0])를 확인하면 말라리아 군집사례 현황을 다룬 표 데이터가 나온다 — 단, 이 값은 §4에서 본 것과 같은 방식으로 unstructured가 원본 그대로 추출한 문자열이라 "인천 서울 1 (0.00) 1 (10개) 5..." 처럼 행·열 구분이 사라져 있다. 이는 우리가 만든 요약본이 아니라 docstore에 저장해 둔 원본이 검색 결과로 그대로 반환됐다는 뜻이다 — 검색은 요약으로, 반환은 원본으로 하는 이 검색기의 설계가 여기서 드러난다.

답변 생성. 검색기가 올바른 결과를 반환하는 것을 확인했으므로, 이를 바탕으로 답변을 생성하는 체인을 구성한다.

from langchain.schema.runnable import RunnablePassthrough, RunnableLambda

def prompt_func(dict):
    format_texts = "\n".join(dict["context"]["texts"])
    text = f"""
다음 문맥에만 기반하여 질문에 답하세요. 문맥에는 텍스트, 표, 그리고 아래 이미지가 포함될
수 있습니다:
질문: {dict["question"]}
텍스트와 표:
{format_texts}
"""
    return [
        HumanMessage(
            content=[
                {"type": "text", "text": text},
                {
                    "type": "image_url",
                    "image_url": {
                        "url": f"data:image/jpeg;base64,{dict['context']['images'][0]}"
                    },
                },
            ]
        )
    ]

model = ChatOpenAI(temperature=0, model="gpt-4o", max_tokens=1024)

# RAG 파이프라인
chain = (
    {
        "context": retriever | RunnableLambda(split_image_text_types),
        "question": RunnablePassthrough(),
    }
    | RunnableLambda(prompt_func)
    | model
    | StrOutputParser()
)

사용자가 질문을 입력하면 그 질문으로 검색기를 돌려 관련 텍스트·테이블·이미지를 찾고, 이를 prompt_func로 정리해 답변 생성을 요청하는 RAG 파이프라인이다. question 자리에는 질문을 가공 없이 그대로 넘기는 RunnablePassthrough()를, context 자리에는 검색기 결과를 split_image_text_types로 가공하는 RunnableLambda를 각각 쓴다(선행 용어 — 러너블·LCEL). 다만 prompt_func가 이미지를 dict['context']['images'][0]처럼 첫 번째 한 장만 프롬프트에 넣는다는 점은 눈여겨봐야 한다 — 검색된 이미지가 여러 장이어도 모델에 보내는 이미지 수를 하나로 제한해 토큰 비용과 프롬프트 복잡도를 낮추는 절충이며, 그 대가로 첫 번째 이미지가 질문과 가장 관련 있다는 보장은 없다.

참고 — RunnableLambda·RunnablePassthrough 복습. 1장 §3에서 다룬 러너블 중, RunnableLambda는 파이썬 람다 함수를 체인 안에서 실행하는 러너블이고 RunnablePassthrough는 입력을 그대로 다음 단계로 전달하는 러너블이다.

```python from langchain.schema.runnable import RunnableLambda my_lambda = RunnableLambda(lambda x: x.upper()) print(my_lambda.invoke("hello world")) # "HELLO WORLD"

from langchain.schema.runnable import RunnablePassthrough passthrough = RunnablePassthrough() print(passthrough.invoke("no change needed")) # "no change needed" ```

체인을 구성했으므로 chain.invoke로 파이프라인을 실행한다.

chain.invoke("말라리아 군집 사례는 어떤가요?")
'말라리아 군집 사례는 총 4개 시도(경기, 서울, 인천, 강원)에서 46건이 확인되었습니다. 인천에서는
5건의 군집 사례가 발생하여 총 10명의 환자가 보고되었습니다. 인천의 군집 사례는 서구(2건),
강화군(1건), 연수구(1건), 중구(1건)에서 발생했습니다.'

저장해 둔 이미지·텍스트 데이터에 기반해 말라리아 군집 사례에 대한 구체적인 답변을 생성하는 것을 확인할 수 있다. 프롬프트가 "다음 문맥에만 기반하여 질문에 답하세요"라고 명시한 것은, 모델이 학습 시 알고 있던 일반 지식이 아니라 검색으로 가져온 문맥에만 근거해 답하도록 제한해 검색되지 않은 내용을 지어내는 것을 줄이기 위해서다.

지금까지 실습을 통해 멀티모달 RAG가 무엇인지, 이를 어떻게 활용할 수 있는지 알아보았다. 멀티모달 RAG는 텍스트뿐만 아니라 이미지·표·차트 등 다양한 데이터 유형을 결합해 더 유용하고 정확한 답변을 제공하는 방식으로, 복잡한 질문에 대한 답변이 요구되는 상황에서 정보의 정확성과 신뢰성을 높이는 데 효과적이다.

핵심 개념 정리

개념 한 줄 설명
멀티모달 RAG 텍스트 외 이미지·차트·표까지 포함해 검색·생성하는 RAG
모달리티 / 멀티모달 데이터의 형태(이미지·텍스트·음성) / 서로 다른 형태를 동시에 다루는 방식
구현 방법 3가지 동일 벡터 공간(CLIP) · 기본 모달리티 통일(요약) · 별도 저장소 + 리랭킹
CLIP 이미지·텍스트를 대조 학습으로 같은 벡터 공간에 매핑하는 모델
MLLM 이미지를 직접 이해하는 멀티모달 LLM(예: gpt-4o)
unstructured / partition_pdf PDF에서 텍스트·표·이미지를 유형별로 추출하는 라이브러리·함수
chunking_strategy basic(글자 수 기준) vs by_title(섹션·페이지 경계 기준)
텍스트·테이블·이미지 요약 원본을 검색에 최적화된 형태로 압축해 벡터화 대상으로 삼는 전처리
MultiVectorRetriever 원본(docstore)과 요약 임베딩(vectorstore)을 id_key로 연결해 검색하는 검색기
base64 인코딩 이진 이미지 데이터를 텍스트로 바꿔 MLLM에 전달하는 방식
detail 파라미터 이미지 처리 품질·토큰 비용을 조절(low·high·auto)

실무 체크리스트

  • [ ] 프로젝트 데이터에 이미지·표가 섞여 있다면, 멀티모달 RAG가 정말 필요한지부터 판단했는가?
  • [ ] 세 가지 구현 방법(동일 벡터 공간·기본 모달리티 통일·별도 저장소) 중 예산·인프라 제약에 맞는 것을 골랐는가?
  • [ ] partition_pdf 호출 전 tesseract → poppler → unstructured[all-docs] 설치 순서(의존성)를 지켰는가?
  • [ ] chunking_strategy를 문서 특성(단순 텍스트 vs 섹션 구분 필요)에 맞게 골랐는가?
  • [ ] 텍스트·테이블·이미지를 요약해 임베딩하기 전, docstore에는 원본을 손실 없이 그대로 보존했는가?
  • [ ] MLLM에 이미지를 전달할 때 detail 파라미터로 토큰 비용을 통제했는가?
  • [ ] 검색된 이미지가 여러 장일 때, prompt_func가 몇 장까지 모델에 전달할지 명시적으로 결정했는가?
  • [ ] 요약 프롬프트에 "검색에 최적화된 요약"임을 명시해, 답변용 설명과 혼동하지 않았는가?
  • [ ] doc_ids·table_ids·img_ids가 서로 겹치지 않아 id_key 연결이 꼬이지 않는지 확인했는가?
  • [ ] InMemoryStore를 실제 서비스에 쓸 계획이라면, 영속적인 저장소로 교체할 계획을 세웠는가?

연습문제

  1. 적용. 회사의 제품 카탈로그가 텍스트 설명과 제품 사진으로 이루어져 있고, 임베딩 모델을 하나만 유지보수하고 싶을 만큼 예산이 빠듯하다면 §2의 세 가지 구현 방법 중 무엇을 고르겠는가? 근거를 들어 설명하라.
  2. 분석. 이 실습이 원본 이미지가 아니라 이미지 요약 텍스트만 벡터저장소에 저장하는 이유는 무엇인가? 이 설계가 한계를 드러낼 수 있는 상황을 하나 들어 논하라.
  3. 설계. 회사의 재무 보고서 PDF에는 텍스트·표·차트 이미지가 섞여 있다. partition_pdfchunking_strategybasicby_title 중 무엇으로 설정할지, 근거를 들어 설계하라.
  4. 판단. MultiVectorRetriever가 검색한 결과에 이미지 3장, 텍스트 1개가 섞여 나왔는데 prompt_func는 이미지 중 첫 번째만 사용한다. 이 설계의 위험을 지적하고 개선 방향을 제시하라.

최신 동향 (2026-09 기준)

최신 동향 (검증 2026-09-12) — 이 장이 설명하는 멀티모달 RAG의 원리(세 가지 구현 방식·멀티벡터 검색기 설계)는 책 내용이 그대로 유효하다. 아래는 이 장의 실습 코드가 의존하는 라이브러리·모델 표기 중 시간이 지나며 바뀐 것만 갱신 대상이다.

  • LangChain의 Chroma 임포트 경로가 바뀌었다. 이 장 §5의 from langchain_community.vectorstores import Chroma는 책이 쓰인 시점의 표준 경로였다. 이후 langchain_community.vectorstores.Chroma는 폐기 예정(deprecated)으로 지정됐고, 별도 패키지 langchain-chromalangchain_chroma.Chroma로 이전됐다. 새로 작성하는 코드라면 pip install langchain-chromafrom langchain_chroma import Chroma를 쓴다. LangChain 공식 Chroma 통합 문서
  • 이 장이 예시로 쓴 gpt-4·gpt-4o는 이전 세대 모델 표기다. 두 모델은 2026-02-13 ChatGPT UI에서 물러났지만 API로는 계속 제공되고 있으며(2026-09 기준), OpenAI의 주력 계열은 GPT-5 세대로 넘어갔다. 이 장의 코드 자체는 원문 그대로이며, 새 프로젝트에서 비전 지원 모델을 고를 때는 OpenAI 공식 배포 종료(deprecations) 문서에서 현재 권장 모델을 확인한다.

부록 A. 핵심 비교표

멀티모달 RAG 구현 방법 세 가지

구분 동일 벡터 공간 기본 모달리티 통일 별도 저장소
핵심 아이디어 CLIP 등으로 모든 모달리티를 같은 벡터 공간에 인코딩 이미지 등을 텍스트 요약으로 바꿔 하나의 모달리티로 통일 두 방식을 결합, 모달리티별 저장소 분리 + 리랭킹
구현 난이도 낮음(임베딩 모델만 교체) 낮음(요약 생성 단계만 추가) 높음(저장소 이중화·리랭킹 필요)
필요한 모델 통합 멀티모달 임베딩 모델(CLIP 등) 확보 멀티모달 LLM(요약용) 멀티모달 임베딩 모델 + 텍스트 임베딩 모델
주요 한계 다양한 콘텐츠를 정확히 임베딩할 모델 확보가 관건 세부 뉘앙스·정보 손실 가능 전처리 시간·인프라 비용이 배로 증가
이 장의 선택 실습에서 채택(§2.2·§3~§6)

docstore vs vectorstore (MultiVectorRetriever 내부)

구분 docstore vectorstore
저장하는 것 원본 텍스트·테이블·이미지(base64) 텍스트·테이블·이미지의 요약 임베딩
이 실습의 구현체 InMemoryStore()(메모리, 비영속) Chroma(임베딩 검색)
쓰이는 시점 답변 생성 — 손실 없는 원본 제공 검색 — 유사도로 빠르게 후보를 찾음
연결 키 각 원본의 uuid 메타데이터 {id_key: uuid}

chunking_strategy — basic vs by_title

구분 basic by_title
조각 기준 글자 수 페이지·섹션 경계
적합한 문서 구조가 단순한 텍스트 제목·섹션이 뚜렷한 보고서·문서
이 장의 선택 채택(§4) — 말라리아 소식지가 섹션 구조를 가짐

부록 B. 추천 참고 자료

외부 자료 (Tier 1 공식·원논문, 생존 확인 2026-09-12)

더 해보기 — 읽고 끝내지 않으려면

  • §4의 partition_pdf를 자신의 PDF(예: 회사 보고서)에 돌려 보고, chunking_strategybasicby_title로 각각 실행해 조각 수·내용이 어떻게 달라지는지 비교해 본다.
  • §6의 prompt_func를 고쳐 이미지를 첫 번째 한 장이 아니라 검색된 이미지 전부를 프롬프트에 담아 보고, 답변 품질과 토큰 비용이 어떻게 바뀌는지 관찰해 본다.

본 책 연계 챕터

챕터 이 장이 다루지 않은 것
2장 §4 벡터 데이터베이스 이 장 §5가 그대로 쓰는 Chroma·벡터 저장소 구성의 전제(임베딩·유사도 검색의 기초)
4장 §4 문서 후처리 이 장 §2.3이 이름만 언급한 리랭킹의 구체적 알고리즘(LLM 기반 리랭킹·크로스 인코더)
1장 §3 랭체인 표현 언어(LCEL) 이 장 §4·§6의 \| 체인 구성 문법과 러너블(Runnable)의 원리

부록 C. 연습문제 풀이

  1. (구현 방법 선택 — 예산 제약) 임베딩 모델을 하나만 유지보수하고 싶다면 §2.1 "동일 벡터 공간(CLIP)" 방식이 가장 적합하다. 이미지·텍스트를 하나의 통합 임베딩 모델로 처리하므로 별도의 요약 생성 단계나 이중 저장소가 필요 없고, 기존 텍스트 기반 RAG 인프라를 그대로 유지하면서 임베딩 모델만 CLIP류로 교체하면 되기 때문이다. 다만 제품 사진처럼 미묘한 디자인 차이를 구별해야 하는 콘텐츠라면, 통합 모델의 임베딩 정확도가 충분한지 사전에 검증해야 한다.

  2. (원본 대신 요약을 임베딩하는 이유 + 한계) unstructured가 추출한 원본 이미지·텍스트·테이블은 검색에 최적화된 형태가 아니라 그대로 임베딩하면 관련 없는 세부 정보 때문에 유사도 검색의 정확도가 떨어진다. 반면 "검색에 최적화된 간결한 요약"은 핵심만 남겨 질문과의 유사도 매칭이 정확해진다. 다만 이 설계는 원본에 있지만 요약에서 빠진 세부 정보(예: 이미지 속 작은 숫자나 각주)에 대한 질문에는 취약하다 — 요약이 놓친 내용은애초에 검색 후보로 올라오지 않기 때문이다.

  3. (chunking_strategy 설계 — 재무 보고서) 재무 보고서처럼 "서론·재무제표·주석" 같은 제목·섹션 구분이 뚜렷한 문서는 by_title이 적합하다. 섹션 경계를 기준으로 조각을 내야 하나의 조각 안에 서로 다른 주제(예: 손익계산서와 주석)가 섞이지 않고, 이후 요약·검색 단계에서 문맥이 흐려지지 않는다. 반대로 섹션 구분이 없는 단순 서술형 텍스트라면 basic으로 충분하다.

  4. (첫 번째 이미지만 쓰는 설계의 위험 + 개선) 검색된 이미지 3장 중 첫 번째가 질문과 가장 관련 있다는 보장이 없으므로, 정작 답변에 필요한 시각 정보가 두 번째·세 번째 이미지에 있다면 모델이 그 정보를 보지 못한 채 답한다. 개선 방향은 이미지 수를 늘려 전달하거나(비용 증가와 맞바꿔), 이미지 요약 임베딩과 질문의 유사도 점수로 상위 1~2장만 골라 전달하는 리랭킹 단계를 추가하는 것이다.

난이도
에피소드
질문
카드를 로딩 중...
답변

클릭하거나 Space를 눌러 뒤집기

0 / 0
학습 진도 0%
이동   Space 뒤집기   R 셔플   P 음성 버전 2026-08-05b